﻿using Grow.Db.Repository;
using Grow.Db.UnitOfWork;
using Grow.EventBus.Cap.Enum;
using Grow.EventBus.Cap.EventDataSend;
using Grow.EventBus.Cap.PublisherEventData;
using Grow.Module.Service;

namespace Grow.EventBus.Cap.PublisherHandler
{
    public class PublisherHandler : IPublisherHandler
    {
        private readonly IServiceContext _serviceContext;
        private readonly EventBusOptions _eventBusOptions;
        private readonly IEventDataSend _eventDataSend;
        public PublisherHandler(IServiceContext serviceContext)
        {
            _serviceContext = serviceContext;
            _eventBusOptions = _serviceContext.GetOptions<EventBusOptions>();
            _eventDataSend = _serviceContext.GetRequiredService<IEventDataSend>();
        }

        public async Task ExecuteAsync(PublisherEventDataDto publisherEventDataDto, CancellationToken cancellationToken = default)
        {
            var failCount = 1;
            while (!cancellationToken.IsCancellationRequested)
            {
                // 执行真正的消息发送
                if (!await EventDataSendAsync(publisherEventDataDto, cancellationToken))
                {
                    failCount++;
                }
                else
                {
                    return;
                }
                if (failCount > _eventBusOptions.RetryFailed)
                {
                    return;
                }
            }
        }

        private async Task<bool> EventDataSendAsync(PublisherEventDataDto publisherEventDataDto, CancellationToken cancellationToken)
        {
            try
            {


                if (await LockPublisherEventDataAsync(publisherEventDataDto.EventDataId, cancellationToken))
                {
                    // 这里可能存在的是消息发送成功,数据库更新失败,那么就可能存在重复发送的情况,这个需要消费方自行冥等处理
                    // 事务已提交,执行消息发送和更新状态
                    await _eventDataSend.ExecuteAsync(publisherEventDataDto, cancellationToken);

                    // 消息发送没问题就更新数据库状态
                    await UpdatePublisherEventDataAsync(publisherEventDataDto.EventDataId, EventDataStatusEnum.Success, cancellationToken);
                }
                return true;
            }
            catch
            {
                try
                {
                    await UpdatePublisherEventDataAsync(publisherEventDataDto.EventDataId, EventDataStatusEnum.Fail, cancellationToken);
                }
                catch
                {
                }
                return false;
            }
        }



        private async Task<bool> LockPublisherEventDataAsync(Guid eventDataId, CancellationToken cancellationToken)
        {
            var now = DateTimeOffset.Now;
            var lockEndTime = DateTimeOffset.Now.AddSeconds(_eventBusOptions.LockTime);
            var serviceContext = _serviceContext.GetServiceContext();
            var unitOfWorkManager = serviceContext.GetRequiredService<IUnitOfWorkManager>();
            var publisherEventDataRepository = serviceContext.GetRequiredService<IRepository<PublisherEventDataEntity>>();

            var publisherEventDataEntity = await publisherEventDataRepository.GetSelectRepository().Where(a =>
                 a.Id == eventDataId
                && (a.LockEndTime < now || a.LockEndTime == null)).FirstOrDefaultAsync( cancellationToken);

            if (publisherEventDataEntity is null)
            {
                return false;
            }
            publisherEventDataEntity.LockEndTime = lockEndTime;
            await publisherEventDataRepository.UpdateAsync(publisherEventDataEntity, cancellationToken);

            await unitOfWorkManager.CommitAsync(cancellationToken);
            return true;
        }

        private async Task UpdatePublisherEventDataAsync(Guid eventDataId, EventDataStatusEnum eventDataStatus, CancellationToken cancellationToken)
        {
            var serviceContext = _serviceContext.GetServiceContext();
            var unitOfWorkManager = serviceContext.GetRequiredService<IUnitOfWorkManager>();
            var publisherEventDataRepository = serviceContext.GetRequiredService<IRepository<PublisherEventDataEntity>>();

            var publisherEventDataEntity = await publisherEventDataRepository.GetSelectRepository().Where(a =>
                a.Id == eventDataId
               ).FirstOrDefaultAsync( cancellationToken);

            if (publisherEventDataEntity is null)
            {
                return;
            }
            publisherEventDataEntity.EventDataStatus = eventDataStatus;
            publisherEventDataEntity.RetryCount++;
            if (eventDataStatus == EventDataStatusEnum.Success)
            {
                publisherEventDataEntity.ExpireTime = DateTimeOffset.Now.AddHours(_eventBusOptions.SucceedExpireHour);
            }
            await publisherEventDataRepository.UpdateAsync(publisherEventDataEntity, cancellationToken);

            await unitOfWorkManager.CommitAsync(cancellationToken);
        }
    }
}
